//
// LoadAppdic.cpp
// LoadAppdic
// Loads the APPDIC with definitions from extract.<applnbr> file
// Copyright (C) 2003, 2004, 2005 eXegeSys, Inc.
// Copyright (C) 2008 Bruce A. James
//
// This program is part of the Protea Project.
//
// The Protea Project is free software; you can redistribute it and/or modify it 
// under the terms of the GNU General Public License as published by the 
// Free Software Foundation, either version 3 of the License, or (at your 
// option) any later version.
//
// The Protea Project is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
// for more details.
// 
// You should have received a copy of the GNU General Public License along 
// with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// Please send correspondence to:
// 
// theproteaproject@gmail.com
//

#include <xercesc/sax2/XMLReaderFactory.hpp>
#include <xercesc/sax2/DefaultHandler.hpp>
#include <xercesc/sax2/Attributes.hpp>
#include <xercesc/framework/XMLFormatter.hpp>

#include "../SDK/ToolsInterface.h"
#include "../OSDep/utf8.h"
#include "../Security/base64.h"
#include "../Security/encrypt.h"

//
// Constants
//
const long MASTER_APP = 1;

const int MODE_NEW = 0;
const int MODE_DELETE = 1;
const int MODE_INCLUDE = 2;
const int MODE_EXCLUDE = 4;


const char backspace = '\b' ;
const char enterKey = '\r' ;
const char newLine  = '\n' ;


//
// Structure declarations
//
struct fieldinfo {
	char name[ 33 ];
	int number;

	int type;
	int length;
	int flags;

	bool isstring;
	bool usenull;
	bool first;
	char data[ 256 ];
	int len;
};



//
// Global variables
//
XCDBTransaction* txn = NULL;
int app_number;
bool HasApplNbr;
char inexname[ 255 ];
int op_mode = MODE_NEW;
long buffernumber;
char buffername[ 32 ];
char sqlbufname[ 32 ];
fieldinfo values[99];
int db_type;

bool GotData = false;
bool FirstRun = true;

int valcount;
int fieldcount;

char* SQL = NULL;
char app_string[ 10 ];

char* new_appdic = NULL;
long new_appdic_size = 0;
buffer_header* header;
buffer_db_defn* database;

// New command line parameters
char cl_name[ 204 ];		// Database name
char cl_user[ 36 ];			// User name
char cl_pass[ 36 ];			// Password
long cl_type;				// Database type

//
// Class declarations
//
class XCLoadAppdic : public XCAppInterface, public XERCES_CPP_NAMESPACE::DefaultHandler {
public:
	XCLoadAppdic() {
		errors = false;
		dbi = NULL;
		Application = false;
		InBuffer = false;
		InStructure = false;
		InRecord = false;
		InField = false;
		GotData = false;
	};

	int Initialize() {
		int result = XCAppInterface::Initialize( "LoadAppdic", 1 );
		dbi = GetDatabase()->GetConnection( 1 );
		assert( dbi );
		return result;
	};

	// Xerces error handlers...
	bool errors;
	void warning( const XERCES_CPP_NAMESPACE::SAXParseException& exception ) { cout << "WARNING: " << UniTranslator(exception.getMessage()).UTF8() << endl; };
	void error( const XERCES_CPP_NAMESPACE::SAXParseException& exception ) { cout << "ERROR: " << UniTranslator( exception.getMessage() ).UTF8() << endl; };
	void fatalError( const XERCES_CPP_NAMESPACE::SAXParseException& exception ) { cout << "FATAL: " << UniTranslator( exception.getMessage() ).UTF8() << endl; };

	// SAX Parser
	void startElement( const XMLCh* const uri, const XMLCh* const localname, const XMLCh* const qname, const XERCES_CPP_NAMESPACE::Attributes& atts ) {
		UniTranslator name( localname );

//		printf( "<%s>", name.UTF8() );
		if (!Application) {
			if (strcmp( name, "Application" )==0) {
//				printf( "Application: %s\n", name );
				HandleApplication( true, &atts );
			};
		} else {
			if (strcmp( name, "Buffer" )==0) {
//				printf( "Buffer: %s\n", name );
				HandleBuffer( true, &atts );
			} else if (strcmp( name, "STRUCTURE" )==0) {
//				printf( "Structure: %s\n", name );
				HandleStructure( true, &atts );
			} else if (strcmp( name, "RECORD" )==0)
				HandleRecord( true, &atts );
			else if (strcmp( name, "Field" )==0)
				HandleField( true, &atts );
			else {
				cout << "Bad XML Start Tag: " << name.UTF8() << endl;
			};
		};
	};

	void characters( const XMLCh* const chars, const unsigned int length ) {
		if (InRecord && InField) {
			fieldinfo* field = &values[ valcount ];
			if (app_number != MASTER_APP && strcmp( field->name, "APPL_NBR" ) == 0) {
				strcpy( field->data, app_string );
			} else if (strcmp( field->name, "DATABASE_NBR" ) == 0 ) {
				strcpy( field->data, app_string );
			} else {
				UniTranslator data( chars );
//				printf( "\nField %s value = \"%s\"\n", field->name, data.UTF8() );
				if (field->first) {
					field->first = false;
					strcpy( field->data, data );
				} else
					strcat( field->data, data );
			};
			field->len = strlen( field->data );
			GotData = true;
		};
	};

	void endElement( const XMLCh* const uri, const XMLCh* const localname, const XMLCh* const qname ) {
		UniTranslator name( localname );

//		printf( "</%s>", name.UTF8() );
		if (strcmp( name, "Application" )==0) {
//			printf( "Application (exit): %s\n", name );
			HandleApplication( false, NULL );
		} else if (strcmp( name, "Buffer" )==0) {
//			printf( "Buffer (exit): %s\n", name );
			HandleBuffer( false, NULL );
		} else if (strcmp( name, "STRUCTURE" )==0) {
//			printf( "Structure (exit): %s\n", name );
			HandleStructure( false, NULL );
		} else if (strcmp( name, "RECORD" )==0)
			HandleRecord( false, NULL );
		else if (strcmp( name, "Field" )==0)
			HandleField( false, NULL );
	};

	void HandleApplication( bool start, const XERCES_CPP_NAMESPACE::Attributes* atts );
	void HandleBuffer( bool start, const XERCES_CPP_NAMESPACE::Attributes* atts );
	void HandleStructure( bool start, const XERCES_CPP_NAMESPACE::Attributes* atts );
	void HandleRecord( bool start, const XERCES_CPP_NAMESPACE::Attributes* atts );
	void HandleField( bool start, const XERCES_CPP_NAMESPACE::Attributes* atts );

	void GetSQLName();

	void ShowPassMessage() {
		cout << endl;
		if (FirstRun) {
			if (op_mode == MODE_DELETE) {
				cout << "Parsing input file: Deleting data from APPDIC tables" << endl << endl;
			} else if (op_mode == MODE_NEW && app_number == MASTER_APP) {
				cout << "Parsing input file: APPDIC Table creation" << endl << endl;
			} else
				cout << "Parsing input file: Selective data removal" << endl << endl;
		} else
			cout << "Parsing input file: Data insertion" << endl << endl;
	};

	XCDBConnection* dbi;

	bool Application;
	bool InBuffer;
	bool InStructure;
	bool InRecord;
	bool InField;
	bool GotData;
};



////////////////////////////////////
//
// MISC FUNCTIONS
//
////////////////////////////////////

// Returns true if the table should be updated or not
bool UseTable( char* name ) {
	bool result = true;

	if (op_mode & MODE_INCLUDE)
		result = (strcmp( name, inexname )==0);
	else if (op_mode & MODE_EXCLUDE)
		result = (strcmp( name, inexname )!=0);

//	printf( "UseTable: %s = %d\n", name, result );
	return result;
};



// Get actual name of table - used for merging into existing appdic
void XCLoadAppdic::GetSQLName() {
	buffer_format* format = GetFormatInfo( buffernumber );
	if (format)
		strcpy( sqlbufname, format->name );
	else
		strcpy( sqlbufname, buffername );
};






////////////////////////////////////
//
// XML Parsing functions
//
////////////////////////////////////


void XCLoadAppdic::HandleField( bool start, const XERCES_CPP_NAMESPACE::Attributes* attrib ) {
	if (UseTable( buffername )) {
		InField = start;
		if (start) {
			if (InStructure) {
				fieldinfo* FieldInfo = &values[ fieldcount ];
				*FieldInfo->data = 0;

				int len = attrib->getLength();
				for (int i=0; i<len; i++) {
					UniTranslator name( attrib->getLocalName( i ) );
					UniTranslator value( attrib->getValue( i ) );

					if (strcmp( name, "Name" )==0) {
						strcpy( FieldInfo->name, value );
						if (strcmp( FieldInfo->name, "APPL_NBR" )==0)
							HasApplNbr = true;
					} else if (strcmp( name, "Type" )==0)
						FieldInfo->type = atoi( value );
					else if (strcmp( name, "Length" )==0)
						FieldInfo->length = atoi( value );
					else if (strcmp( name, "Flags" )==0)
						FieldInfo->flags = atoi( value );
				};

//				printf( "ADD FIELD [%s]\n", FieldInfo->name );
				if (app_number == MASTER_APP) {
					char temp[ 255 ];
					if (fieldcount)
						strcat( SQL, "," );
					strcat( SQL, FieldInfo->name );

					switch( FieldInfo->type ) {
						case S_INTEGER:
							strcat( SQL, " SMALLINT DEFAULT 0 NOT NULL" );
							break;

						case AUTONUMBER:
						case LITERAL_VAL:
						case D_INTEGER:
							strcat( SQL, " INTEGER DEFAULT 0 NOT NULL" );
							break;

						case STRING:
							sprintf( temp, " CHAR(%d) DEFAULT ''%s",
								FieldInfo->length,
								FieldInfo->flags != 512 ? " NOT NULL" : "" );
							strcat( SQL, temp );
							break;

						case XCBOOL:
							strcat( SQL, " CHAR(1) DEFAULT '0' NOT NULL" );
							break;

						case DATE_TIMESTAMP:
							if (db_type == db_mssql7 || db_type == db_sybase)
								strcat( SQL, " DATETIME DEFAULT NULL" );
							else
								strcat( SQL, " TIMESTAMP DEFAULT NULL" );
							break;

						case TIME_24HR_DB:
							if (db_type == db_oracle)
								strcat( SQL, " TIMESTAMP DEFAULT NULL" );
							else if (db_type == db_mssql7 || db_type == db_sybase)
								strcat( SQL, " DATETIME DEFAULT NULL" );
							else
								strcat( SQL, " TIME DEFAULT NULL" );
							break;

						case DATE_DB:
							if (db_type == db_oracle)
								strcat( SQL, " TIMESTAMP DEFAULT NULL" );
							else if (db_type == db_mssql7 || db_type == db_sybase)
								strcat( SQL, " DATETIME DEFAULT NULL" );
							else
								strcat( SQL, " DATE DEFAULT NULL" );
							break;

						default:
							strcat( SQL, " UNKNOWN" );
							break;
					};	// switch
				};	// if app = MASTER_APP

				// Determine if the value is a string or not
				int type = FieldInfo->type;
				fieldinfo* field = &values[ fieldcount ];
				field->isstring = !(
					type == S_INTEGER ||
					type == D_INTEGER ||
					type == AUTONUMBER ||
					type == DEC_FLOAT ||
					type == DEC_DOUBLE ||
					type == LITERAL_VAL );
				if (field->isstring)
					field->usenull = ((type == DATE_DB) || (type == DATE_TIMESTAMP) || (type == TIME_24HR_DB));

				fieldcount++;
			} else if (InRecord) {	// Field record
				if (valcount)
					strcat( SQL, "," );
				strcat( SQL, values[ valcount ].name );
				GotData = false;
			};
		} else {
			if (InRecord) {
				if (!GotData) {
					fieldinfo* field = &values[ valcount ];
					*field->data = 0;
					field->len = 0;
				};
				valcount++;
			};
		};
	} else
		InField = false;
};




void XCLoadAppdic::HandleRecord( bool start, const XERCES_CPP_NAMESPACE::Attributes* atts ) {
	if (!FirstRun) {
		InRecord = start;
		if (start) {
			// Clear data from fields
			fieldinfo* temp = values;
			for (int i=0; i<fieldcount; i++) {
				*temp->data = 0;
				temp->first = true;
			};

			valcount = 0;
			if (!(op_mode & MODE_DELETE) && UseTable( buffername )) {
				sprintf( SQL, "INSERT INTO %s (", sqlbufname );
			};
		} else {
			if (!(op_mode & MODE_DELETE) && UseTable( buffername )) {
				strcat( SQL, ") values (" );
 
				int len = strlen( SQL );
				fieldinfo* temp = values;
				for (int i=0; i<valcount; i++) {
					if (i)
						SQL[len++] = ',';

					if (temp->isstring) {
						if (temp->data && *temp->data) {
							SQL[len++] = '\'';
							for (int j=0; j<temp->len; j++) {
								if (temp->data[j] != '\'')
									SQL[len++] = temp->data[j];
								else {
									SQL[len++] = '\'';
									SQL[len++] = '\'';
								};
							};
							SQL[len++] = '\'';
						} else {
							if (temp->usenull) {
								strcpy( &SQL[ len ], "NULL" );
								len += 4;
							} else {
								strcpy( &SQL[ len ], "''" );
								len += 2;
							};
						};
					} else {
						memcpy( &SQL[ len ], temp->data, temp->len );
						len += temp->len;
					};
					*temp->data = 0;

					temp++;
					SQL[len] = 0;
				};
				SQL[len++] = ')';
				SQL[len] = 0;

				XCDBStatement* stmt = txn->CreateStmt( NULL );
				try {
//					cout << "Buffer command: " << SQL << endl;
					stmt->Execute( SQL );
				} catch( ... ) {
					cout << "Bad command: " << SQL << endl;
					if (stmt) {
						stmt->Release();
						stmt = NULL;
					};
					throw;
				};
				if (stmt) {
					stmt->Release();
					stmt = NULL;
				};
			};
		};
	}; // If first run
};




void XCLoadAppdic::HandleStructure( bool start, const XERCES_CPP_NAMESPACE::Attributes* attrib ) {
	InStructure = start;
	if (start) {
		GetSQLName();
		if (FirstRun && app_number == MASTER_APP && UseTable( buffername ))
			sprintf( SQL, "CREATE TABLE %s (", buffername );
		fieldcount = 0;
	} else {
		// Determine if we should drop table, delete records, 
		if (op_mode != MODE_NEW && UseTable( buffername ) && FirstRun) {

			if (app_number == MASTER_APP && op_mode == MODE_DELETE)
				sprintf( SQL, "DROP TABLE %s", sqlbufname );
			else if (buffernumber != 1 || op_mode == MODE_DELETE) {
				if (HasApplNbr)
					sprintf( SQL, "DELETE FROM %s WHERE APPL_NBR = %d", sqlbufname, app_number );
				else
					sprintf( SQL, "DELETE FROM %s", sqlbufname );
			} else
				*SQL = 0;

			if (*SQL) {
				cout << "Destroy command: " << SQL << endl;
				XCDBStatement* stmt;
				try {
					stmt = txn->CreateStmt( NULL );
					stmt->Execute( SQL );
				} catch( ... ) {
					if (stmt)
						stmt->Release();
					stmt = NULL;
					if (memcmp( "DROP", SQL, 4 )!=0)
						throw;
				};
				if (stmt)
					stmt->Release();
				stmt = NULL;
			};
		};

		if (op_mode == MODE_NEW && FirstRun && app_number == MASTER_APP && *SQL) {
			strcat( SQL, ")" );

			XCDBStatement* stmt = txn->CreateStmt( NULL );
			try {
//				cout << "Structure command: " << SQL << endl;
				stmt->Execute( SQL );
			} catch( ... ) {
				if (stmt)
					stmt->Release();
				stmt = NULL;
				throw;
			};
			*SQL = 0;
			if (stmt)
				stmt->Release();
			stmt = NULL;
		}; // if first

/*
		// Fields we have
		cout << "Fields we have in the buffer: " << endl;
		for (int i=0; i<fieldcount; i++) {
			cout << "  " << values[ i ].name << "  Len=" << values[i].length << endl;
		};
*/
	};
};





void XCLoadAppdic::HandleBuffer( bool start, const XERCES_CPP_NAMESPACE::Attributes* attrib ) {
	InBuffer = start;
	if (start) {
		int len = attrib->getLength();
		for (int i=0; i<len; i++) {
			UniTranslator name( attrib->getLocalName( i ) );
			UniTranslator value( attrib->getValue( i ) );

			if (strcmp( name, "Number" )==0) {
				buffernumber = atoi( value );
			} else if (strcmp( name, "Name" ) ==0) {
				strcpy( buffername, value );
			} else
				printf( "INVALID XML!!\n" );
		};

		cout << "Working on buffer: " << buffername << endl;
		HasApplNbr = false;
		fieldcount = 0;
	};
};




void XCLoadAppdic::HandleApplication( bool start, const XERCES_CPP_NAMESPACE::Attributes* atts ) {
	if (start)
		Application = true;
	else
		Application = false;
};





////////////////////////////////////////////
//
// Functions to create a 'base' appdic file
//
////////////////////////////////////////////


void write_format( int number, char* name, int type, int view_flag=-1 ) {
	// Override:
	view_flag = -1;

	buffer_format* format = (buffer_format*)(new_appdic + new_appdic_size);
	header->formats.count++;

	// Prepare the format descriptor record
	memset( format, 0, sizeof( buffer_format ) );
	format->number = number;
	strcpy( format->name, name );
	if (view_flag != -1) {
		char number[5];
		sprintf( number, "%2.2d", view_flag );
		strcat( format->name, number );
	};

	format->type = type;
	format->indexed = false;

	new_appdic_size += sizeof( buffer_format );
};





void write_formats() {
	header->formats.offset = new_appdic_size;

	write_format( 1, "DATABASE_DEFN", 0, 0 );
	write_format( 2, "DATASET_DESCR", 0, 0 );
	write_format( 3, "DATA_STRUCTURES", 0, 1 );
	write_format( 5, "FIELD_DESCRIPTOR", 0, 5 );
	write_format( 6, "FORMAT_DESCRIPTR", 0, 2 );
};





void write_field( int num, char* name, int type, int size, int flags = 0 ) {
	buffer_field* new_field = (buffer_field*)(new_appdic + new_appdic_size);
	header->fields.count++;

	memset( new_field, 0, sizeof( buffer_field ) );
	new_field->number = num;
	strcpy( new_field->name, name );
	new_field->type = type;
	new_field->length = size;
	new_field->flags = flags;

	new_appdic_size += sizeof( buffer_field );
};



void write_fields() {
	header->fields.offset = new_appdic_size;

	write_field( 1, "DATABASE_NBR", 6, 2 );
	write_field( 2, "DATABASE_NAME", 1, 200 );
	write_field( 3, "DATE_GENERATED", 6, 2 );
	write_field( 4, "DB_USER", 1, 32 );
	write_field( 5, "DB_PASSWORD", 1, 32 );
	write_field( 6, "CUST_VERSION_NBR", 7, 4 );
	write_field( 7, "DB_MODIFY_FLAG", 6, 2 );
	write_field( 8, "FORMAT_NAME", 1, 21 );
	write_field( 9, "FORMAT_NUMBER", 7, 4 );

	write_field( 10, "FIELD_NUMBER", 7, 4 );
	write_field( 11, "FIELD_NAME", 1, 16 );
	write_field( 13, "MIN_LENGTH", 7, 4 );
	write_field( 14, "MAX_LENGTH", 7, 4 );
	write_field( 15, "DATA_TYPE", 6, 2 );
	write_field( 16, "DECIMAL_PLACES", 6, 2 );
	write_field( 17, "CRITICAL_FIELD", 7, 4 );
	write_field( 18, "DESCRIPTOR_FLAGS", 7, 4 );
	write_field( 19, "SEQUENCE_NBR", 6, 2 );

	write_field( 23, "DB_BYTE_LENGTH", 7, 4 );
	write_field( 24, "DB_FIELD_LENGTH", 6, 2 );
	write_field( 25, "FIELD_VALUE", 1, 256, 512 );
	write_field( 26, "FORMAT_TYPE", 6, 2 );
	write_field( 27, "CMND_FORMAT_NAME", 1, 16, 512 );
	write_field( 28, "CRITICAL_FORMAT", 7, 4 );

	write_field( 38, "DB_TYPE", 7, 4 );
	write_field( 39, "VIEW_FLAG", 6, 2 );

	write_field( 41, "APPL_NAME", 1, 254 );
	write_field( 65, "APPL_NBR", 6, 2 );

	write_field( 79, "CHILD_FORMAT", 7, 4 );
};





long DSAlign( long size ) {
	// Set our packing right here!
	int packing = 2;		// 16 bit packing
	if (packing) {
		int temp = size;
		temp += (1 << packing) - 1;
		temp >>= packing;
		temp <<= packing;

		size = temp;
	};

	return size;
};



void add_data_structure( int format, int field_num ) {
	int size;

	static int last_format = 0;
	static buffer_format* bFormat = NULL;
	static int last_offset = 0;

	buffer_ds* data = (buffer_ds*)(new_appdic + new_appdic_size);
	memset( data, 0, sizeof( buffer_ds ) );
	header->ds.count++;

	// Do something about formats
	if (last_format != format) {
		// Get buffer record and index to this record
		if (!bFormat)
			bFormat = (buffer_format*)(new_appdic + header->formats.offset);

		while (bFormat->number != format) {
			//cout << "Change formats" << endl;
			bFormat++;
		};

		bFormat->ds_offset = new_appdic_size;
		last_offset = 0;
		last_format = format;
	};
	bFormat->ds_count++;

	buffer_field* tfield = (buffer_field*)(new_appdic + header->fields.offset);
	for (int i=0; i<header->fields.count; i++) {
		if (tfield->number == field_num)
			i = header->fields.count;
		else
			tfield++;
	};

	//cout << "Adding field: " << tfield->name << " to format " << bFormat->name << endl;

	data->field_offset = ((char*)tfield - new_appdic);
	switch( tfield->type ) {
		case S_INTEGER:
			size = 2;
			data->alloc_size = DSAlign(2);
			break;

		case D_INTEGER:
			size = 4;
			data->alloc_size = DSAlign(4);
			break;

		default:
			size = tfield->length;
			data->alloc_size = DSAlign(tfield->length + 1);
			break;
	};

	data->min_length = 0;
	data->max_length = size;
	data->data_type = tfield->type;
	data->dec_places = tfield->decimal_places;
	data->flags = tfield->flags;
	data->data_offset = last_offset;
	data->index = 0;
	last_offset += data->alloc_size;
	bFormat->length = last_offset;

	new_appdic_size += sizeof( buffer_ds );
};





void write_data_structures() {
	header->ds.offset = new_appdic_size;

	// Database-defn
	add_data_structure( 1, 1 );
	add_data_structure( 1, 2 );
	add_data_structure( 1, 3 );
	add_data_structure( 1, 4 );
	add_data_structure( 1, 5 );
	add_data_structure( 1, 6 );
	add_data_structure( 1, 7 );
	add_data_structure( 1, 38 );
	add_data_structure( 1, 65 );

	// DataSet-Descr
	add_data_structure( 2, 1 );
	add_data_structure( 2, 6 );
	add_data_structure( 2, 9 );
	add_data_structure( 2, 39 );
	add_data_structure( 2, 65 );

	// Data-structures
	add_data_structure( 3, 6 );
	add_data_structure( 3, 9 );
	add_data_structure( 3, 10 );
	add_data_structure( 3, 13 );
	add_data_structure( 3, 14 );
	add_data_structure( 3, 15 );
	add_data_structure( 3, 16 );
	add_data_structure( 3, 17 );
	add_data_structure( 3, 18 );
	add_data_structure( 3, 19 );
	add_data_structure( 3, 65 );

	// Field-Descriptor
	add_data_structure( 5, 10 );
	add_data_structure( 5, 11 );
	add_data_structure( 5, 15 );
	add_data_structure( 5, 16 );
	add_data_structure( 5, 17 );
	add_data_structure( 5, 18 );
	add_data_structure( 5, 23 );
	add_data_structure( 5, 24 );
	add_data_structure( 5, 25 );
	add_data_structure( 5, 65 );

	// Format-Descriptor
	add_data_structure( 6, 6 );		/// cust_vers_nbr
	add_data_structure( 6, 8 );		// name
	add_data_structure( 6, 9 );		/// number
	add_data_structure( 6, 26 );	/// format_type
	add_data_structure( 6, 27 );	// command
	add_data_structure( 6, 28 );	// critical format
	add_data_structure( 6, 65 );	// appl_nbr
	add_data_structure( 6, 79 );	/// child_format
};




void write_dataset_descr() {
	header->db_tables.offset = new_appdic_size;
	database->table_offset = new_appdic_size;

	for (int i=1; i<=14; i++) {
		int j;

		if (i <= 6 || i == 14) {
			buffer_db_tables* table = (buffer_db_tables*)(new_appdic + new_appdic_size);

			table->format = i;

			buffer_format* format = (buffer_format*)(new_appdic + header->formats.offset);
			for (j=0; j<header->formats.count; j++) {
				if (format->number == i)
					j = header->formats.count;
				else
					format++;
			};

			table->db_offset = (char*)database - new_appdic;
			table->view_flag = -1;

			format->dd_offset = (char*)table - new_appdic;
			header->db_tables.count++;
			database->table_count++;
			new_appdic_size += sizeof( buffer_db_tables );

			//cout << "Link format (" << format->number << ") " << format->name << " to database " << database->name << "  offset=" << format->dd_offset << endl;
		};
	};
};




void write_appl_defn() {
	header->appl_defn.offset = new_appdic_size;
	header->appl_defn.count = 1;
	
	// Create the application defn record
	appl_defn* appinfo = (appl_defn*)(new_appdic + new_appdic_size);
	memset( appinfo, 0, sizeof( appl_defn ) );

	// Set application defaults
	appinfo->appl_nbr = 1;
	appinfo->unicode_mult = 1;	// No unicode support
	strcpy( appinfo->appl_name, "BOOTSTRAP" );

	new_appdic_size += sizeof( appl_defn );
};




void generate_new_appdic() {
	write_appl_defn();
	write_formats();
	write_fields();
	write_data_structures();
	write_dataset_descr();
};


bool GetLine( char* szPassword, int max_length, const char password_char=0 ) {
	INFUNC( GetLine, NULL );

	int cInput;
	int n = 0;
	bool done = false;
	bool result = true;
	bool redirected = false;

	LOADAPPDIC_GETLINE;

	OUTFUNCRET( result );
};



void write_database_defn() {

	header->databases.offset = new_appdic_size;
	header->databases.count = 1;

	database = (buffer_db_defn*)(new_appdic + new_appdic_size);
	memset( database, 0, sizeof( buffer_db_defn ) );

	database->number = app_number;

	if ( cl_name[0] )
	{
		strcpy( database->name, cl_name );
	}
	else
	{
		cout << "Database name: ";
		GetLine( database->name, sizeof( database->name )-1 );
	}
	char* upper = database->name;

	// Force the database name to uppercase
	while (*upper) {
		*upper = toupper( *upper );
		upper++;
	};

	if ( cl_user[0] )
	{
		strcpy( database->user, cl_user );
	}
	else
	{
		cout << "Username: ";
		GetLine( database->user, sizeof( database->user )-1 );
	}

	bool good_password = false;
	bool abort = false;

	char pass1[ 256 ];
	char pass2[ 256 ];
	int attempts = 0;
	if ( cl_pass[0] )
	{
		strcpy( pass1, cl_pass );
	}
	else
	{
		int length = (sizeof( database->pass ) / 4 * 3) - 1;

		do {
			cout << "Password: ";
			abort |= !GetLine( pass1, length, '*' );

			cout << "Verify  : ";
			abort |= !GetLine( pass2, length, '*' );

			if (strcmp( pass1, pass2 )==0)
				good_password = true;
			else {
				cout << "Passwords do not match!" << endl << endl;
				attempts++;
				if (attempts == 5)
					abort = true;
			};
		} while (!good_password && !abort);

		if (abort)
			exit(1);
	}

	// Now take the password and encrypt that value
	int resultlen;
	char* result = XCEncrypt( pass1, strlen( pass1 )+1, resultlen );

	// Now take the encrypted value, and save in base64
	XCBase64 base64;
	base64.Encode( database->pass, (unsigned char*)result, resultlen );
	delete [] result;

	attempts = 0;
	if ( cl_name[0] && cl_type )
	{
		database->type = cl_type;
	}
	else
	{
		do {
			char type[5];

			cout << "Database: " << endl;
			cout << "\t1. SQL Server" << endl;
			cout << "\t2. DB2" << endl;
			cout << "\t3. Oracle" << endl;
			cout << "\t4. ODBC" << endl;
			cout << "\t5. MS Access (windows only)" << endl;
			cout << "\t6. MySQL" << endl;
			cout << "\t7. Sybase" << endl;
			cout << "Type: ";
			GetLine( type, 4 );
			database->type = atoi( type );
			attempts++;
		} while ((database->type <= 0 || database->type > 7) && attempts < 6);
	}
	db_type = database->type;
	new_appdic_size += sizeof( buffer_db_defn );

	if (attempts == 6)
		exit(2);
};




void MakeAppdic( XCAppInterface* LoadApp ) {
	new_appdic = new char[ 1024 * 1024 ];
	header = (buffer_header*)new_appdic;
	new_appdic_size = sizeof( buffer_header );
	memset( header, 0, sizeof( buffer_header ) );

	write_database_defn();
	if (app_number == MASTER_APP)
		generate_new_appdic();

	char filename[ MAX_PATH ];
	sprintf( filename, "%sAPPDIC%d.Appdic", LoadApp->ToolsDir, app_number );

	XCFileTransport test( LoadApp );
	test.Open( filename, write_only, create_always );
	test.Send( new_appdic, new_appdic_size );
	test.Close();
};





///////////////////////////////////////////////////
//
// Program entry point
//
///////////////////////////////////////////////////

int main(int argc, char* argv[]) {
	char* text = NULL;
	XCAppOptions myopts;
	int result = 0;
	bool bootstrap = false;

	Protea::ShowIntro( "LoadAppdic" );

	if (argc > 1 && !strcmp(argv[1],"-L"))
	{
		Protea::ShowLicense();
		return 0;
	};

	Protea::Initialize();

	XCLoadAppdic LoadApp;
	try {
		if (argc != 3 && argc != 4 && argc != 8) {
			cout << "Usage: LoadAppdic [ApplicationNumber] [filename]" << endl;
			return 1;
		};

		memset( cl_name, 0, 204 );
		memset( cl_user, 0, 36 );
		memset( cl_pass, 0, 36 );

		SQL = new char[ 65535 ];
		app_number = atoi( argv[1] );

		op_mode = MODE_NEW;
		if (app_number < 0) {
			app_number = abs( app_number );
			op_mode = MODE_DELETE;
		};
		sprintf( app_string, "%d", app_number );

		if (argc == 4 || argc == 8) {
			char* name = argv[3];
			strcpy( inexname, &name[1] );
			if (*name == 'I')
				op_mode |= MODE_INCLUDE;
			else if (*name == 'E')
				op_mode |= MODE_EXCLUDE;
			else if (*name == 'X')
			{
                strncpy( cl_name, argv[4], 204 );
				strncpy( cl_user, argv[5], 36 );
				strncpy( cl_pass, argv[6], 36 );
				cl_type = atoi( argv[7] );
			};
		};

		if (app_number == 0) {
			bootstrap = true;
			app_number = 1;
		};

		// Ensure that the extract file exists
		if (!XCFileExists( argv[2] )) {
			printf( "Extract file not found, aborting.\n" );
			return 2;
		};

		cout << "Loading APPDIC information for application: " << app_number << endl;

		LoadApp.GetOptions( myopts );
		myopts.debug_level_cutoff[ TCL_APPINTERFACE ] = TLV_MEDIUM;
		for ( int i = 0; i < num_tc; i++ )
			myopts.debug_level_cutoff[ i ] = 0xFF;
//		myopts.debug_type_filter = 0xFFFF;
		myopts.debug_type_filter = 0;
		myopts.appdic_first_run = true;
		LoadApp.SetOptions( myopts );

		// Initialize
		if (op_mode == MODE_NEW)
			MakeAppdic( &LoadApp );

		if (bootstrap)
			return 0;

		LoadApp.Initialize();

		// Start database transaction
		LoadApp.ToolsTrace( TRC_APPINTERFACE, TLV_LOW, "Start transaction" );
		txn = LoadApp.dbi->CreateTxn();
		txn->Begin();

		// Start SAX Parse
		LoadApp.ToolsTrace( TRC_APPINTERFACE, TLV_LOW, "Parse XML" );
		XERCES_CPP_NAMESPACE::SAX2XMLReader* parser = XERCES_CPP_NAMESPACE::XMLReaderFactory::createXMLReader();

		parser->setContentHandler( &LoadApp );
		parser->setErrorHandler( &LoadApp );

		// During the first run, we create or delete structure information.
		if (app_number == MASTER_APP || (op_mode != MODE_NEW)) {
			// Interbase is unable to add data to tables that have not been committed,
			// which is why this two phase process is necessary
			FirstRun = true;
			LoadApp.ShowPassMessage();
			parser->parse( argv[2] );

			if (LoadApp.errors) {
				txn->Rollback();
				return 1;
			};

			LoadApp.ToolsTrace( TRC_APPINTERFACE, TLV_LOW, "First run completed" );
			txn->Commit();
			LoadApp.CleanupObjects( 1, &txn );

			// Restart txn for second phase
			txn = LoadApp.dbi->CreateTxn();
			txn->Begin();
		};   // First pass


		// Second pass adds data to the appdic
		FirstRun = false;

		// If we are not deleting, then add the data
		if (op_mode != MODE_DELETE) {
			LoadApp.ShowPassMessage();
			parser->parse( argv[2] );
			delete parser;

			// On parse failure, rollback
			if (LoadApp.errors) {
				txn->Rollback();
				return 1;
			};
		};

		// Add record to DATABASE_DEFN
		XCDBStatement* stmt = txn->CreateStmt( NULL );
		*SQL = 0;

		strcpy( buffername, "DATABASE_DEFN" );
		buffernumber = 1;
		LoadApp.GetSQLName();

		if (op_mode == MODE_DELETE) {
			if (app_number != MASTER_APP) {
				sprintf( SQL, "DELETE FROM %s WHERE APPL_NBR = %d", sqlbufname, app_number );
				stmt->Execute( SQL );
			};
		};
		
		if (op_mode == MODE_NEW) {
//				cout << "Add DB Defn record" << endl;

			bool retry = false;

			do {
				sprintf( SQL, "INSERT INTO %s (DATABASE_NBR, DATABASE_NAME, "
					"DATE_GENERATED, DB_USER, DB_PASSWORD, DB_TYPE, DB_MODIFY_FLAG, "
					"CUST_VERSION_NBR, APPL_NBR) values (%d,'%s',0,'%s','%s',%ld,0,0,%d)",
					sqlbufname,
					app_number,
					database->name,
					database->user,
					database->pass,
					database->type,
					app_number );

				try {
					stmt->Execute( SQL );
					retry = false;
				} catch( ... ) {
					if (!retry) {
						// If it failed, attempt to drop the record and re-add
						sprintf( SQL, "DELETE FROM %s WHERE APPL_NBR = %d", sqlbufname, app_number );
						stmt->Execute( SQL );

						retry = true;
					} else
						throw;
				};
			} while (retry);
		};

		LoadApp.CleanupObjects( 1, &stmt );
		txn->Commit();
		LoadApp.CleanupObjects( 1, &txn );

		// Clean up...
	} catch( XCToolsError& error ) {
		LoadApp.ToolsTrace( TRC_APPINTERFACE, TLV_LOW, "Caught tools error" );
		XCOutputCallStack( &LoadApp, error.GetErrorNumber(),error.GetDescription() );
		if (txn) {
			txn->Rollback();
			LoadApp.CleanupObjects( 1, &txn );
		};
		result = error.GetErrorNumber();
	} catch( ... ) {
		result = 1;
		LoadApp.ToolsTrace( TRC_APPINTERFACE, TLV_LOW, "Caught unknown error" );
		XCOutputCallStack( &LoadApp, -1, NULL );
		if (txn) {
			txn->Rollback();
			LoadApp.CleanupObjects( 1, &txn );
		};
	};

	// Cleanup
	if (SQL)
		delete [] SQL;

	if (text)
		delete [] text;

	if (new_appdic)
		delete [] new_appdic;

	if (result == 0)
		cout << endl << "LoadAppdic process completed successfully." << endl << endl;
	else
		cout << endl << "LoadAppdic process failed with error code:" << result << endl << endl;

	Protea::Uninitialize();

	return result;
};
